function [result, headers, status] = http_get(varargin)
% http_get  - make a single HTTP GET request
%
% http_get uses a binary in the _bin folder to make the actual
% HTTP request to the server, there's no timeout control, so
% make sure to ONLY use it with valid requests
%
% FORMAT:       [result, headers, status] = http_get([srv,] url [,opts])
%
% Input fields:
%       srv         can either be a named server or an IP address,
%                   with or without a preceding 'http://', and a
%                   possible port specification after a colon
%       url         if server is given, the file/partial URL on
%                   the server, otherwise the complete URL (also
%                   with or without the preceding 'http://'
%       opts        struct with any of the following fields
%        .head      if given, perform a HTTP HEAD request instead
%        .range     1x2 double with a valid byte range
%        .timeout   seconds to timeout connection (only Linux!)
%
% Output fields:
%
%       result      the response of the HTTP server with the headers
%                   already stripped
%       headers     the returned headers of the HTTP server
%       status      the returned HTTP status code (e.g. 200)
%
% NOTE:         http_get will automatically follow a '302 Found'
%               status reply to the new location of the file.
%
% Examples:
%
% http_get http://www.microsoft.com/   % displays the HTML
%
% file = http_get('http://www.download.com/somearchive.zip');
%       % downloads somearchive.zip from the server
%       % however, http_getfile is more suitable for large files.
%
% See also http_query, http_getfile, asciiwrite, md5sum.

% Version:  v0.7f
% Build:    8110521
% Date:     Nov-05 2008, 9:00 PM CET
% Author:   Jochen Weber, SCAN Unit, Columbia University, NYC, NY, USA
% URL/Info: http://wiki.brainvoyager.com/BVQXtools

% persistent binary location
persistent http_get_binloc;
if isempty(http_get_binloc)

    http_get_binloc = bvqxtools_path('bin');
    if ispc
        http_get_binloc = [strrep(http_get_binloc, '/', '\') '\http_get.exe'];
    else
        http_get_binloc = [http_get_binloc '/http_get'];
    end
end

% enough arguments ?
if nargin < 1
    error( ...
        'BVQXtools:TooFewArguments',...
        'Too few arguments. Try ''help %s''.',...
        mfilename ...
    );
end

% default values
result = '';
headers = 'HTTPGET/0.6 408 InvalidRequest';
status = 408;
sport  = ':80';
dohead = '';
range  = '';
copts  = [];
tmout  = 0;

server = '';
opts   = struct('EMPTY',1);

% parse input arguments
if nargin == 1 || ...
   ~ischar(varargin{2})
    url = varargin{1};
    if nargin == 2 && ...
        isstruct(varargin{2})
        opts = varargin{2};
    end
else
    server = varargin{1};
    url = varargin{2};
    if nargin > 2 && ...
        isstruct(varargin{3})
        opts = varargin{3};
    end
end
if isfield(opts, 'head')
    dohead = ' -h ';
    copts.head = 1;
end
if  isfield(opts,'range') && ...
    numel(opts.range) == 2 && ...
   ~any(isnan(opts.range(1:2))) && ...
   ~any(isinf(opts.range(1:2))) && ...
   ~any(opts.range(1:2)<0) && ...
    opts.range(1) < opts.range(2)
    range = sprintf(' -r%.0f,%.0f ', opts.range(1:2));
    copts.range = opts.range(1:2);
end
if isfield(opts,'timeout') && ...
   ~isempty(opts.timeout) && ...
   ~isnan(opts.timeout(1)) && ...
   ~isinf(opts.timeout(1))
    tmout = floor(opts.timeout(1) + 0.9999999);
    tmout = tmout * sign(tmout);
    copts.timeout = tmout;
end

if isempty(server)
    if numel(url) > 7 && ...
        all(lower(url(1:7) == 'http://'))
        url(1:7)=[];
    end
    slashes = find(url == '/');
    if isempty(slashes)
        server = url;
        url = '';
    else
        server = url(1:slashes(1)-1);
        url = url(slashes(1)+1:end);
    end
end
if isempty(server)
    return;
end
if isempty(url) || ...
    url(1) ~= '/'
    url = ['/' url];
end

% make sure to not send any invalid chars in URL
rchars = find(url < 46 | url == 58 | url == 60 | url == 62 | ...
    (url > 90 & url < 95) | url == 96 | (url > 122 & url < 126) | ...
    url > 126);
if ~isempty(rchars)
    url = cellstr(url');
    for rc = rchars
        url{rc} = sprintf('%%%02X', double(url{rc}));
    end
    url = gluetostring(url', '');
end

% check for server port
hsport = regexpi(server, ':\d+$');
if ~isempty(hsport)
    sport = server(hsport(1):end);
    server(hsport(1):end) = [];
end

tfname = sprintf('%s_%08d', tempname, floor(rand(1,1)*2^24));

if tmout < 1 || ...
    ispc
    if ispc
        [iresult, istatus] = invsystem(['"' ...
            http_get_binloc '" ' dohead range ...
            ' "http://' server sport '/' url '"  -w' tfname]);
    else
        [iresult, istatus] = invsystem([ ...
            http_get_binloc dohead range ...
            ' ''http://' server sport '/' url ''' -w' tfname]);
    end
else
    [iresult, istatus] = invsystem([ ...
        http_get_binloc dohead range ...
        ' ''http://' server sport '/' url ''' -w' tfname ' -b > ' tfname '.hgs &']);
    if istatus
        try
            if exist([tfname '.hgt'],'file') > 0
                delete([tfname '.hgt']);
            end
        catch
            % nothing
        end
        try
            if exist([tfname '.hgs'],'file') > 0
                delete([tfname '.hgs']);
            end
        catch
            % nothing
        end
        headers = ['HTTPGET/0.6 405 ErrorSendingRequest' char([13,10]) ...
            'Message: ' iresult];
        status = 405;
        return;
    end
    totime = now * 86400 + tmout;
    while exist(tfname, 'file') ~= 2 && ...
       (now*86400) <= totime
        pause(0.2);
        drawnow;
    end
    if exist(tfname, 'file') ~= 2
        headers = 'HTTPGET/0.6 409 Timeout';
        status = 409;
        try
            if exist([tfname '.hgt'],'file') > 0
                delete([tfname '.hgt']);
            end
        catch
            % nothing
        end
        try
            if exist([tfname '.hgs'],'file') > 0
                delete([tfname '.hgs']);
            end
        catch
            % nothing
        end
        return;
    end
    try
        if exist([tfname '.hgs'],'file') > 0
            delete([tfname '.hgs']);
        end
    catch
        % nothing
    end
end

if istatus
    delete(tfname);
    headers = ['HTTPGET/0.6 405 ErrorSendingRequest' char([13,10]) ...
        'Message: ' iresult];
    status = 405;
    return;
end

iresult = asciiread(tfname);
delete(tfname);

if length(iresult) < 32
    headers = 'HTTPGET/0.6 405 ErrorSendingRequest';
    status = 405;
    return;
end

if ~strcmp(iresult(1:25), 'HTTPGET/0.6 222 Infoblock')
    headers = 'HTTPGET/0.6 500 BadBinary';
    status = 500;
    return;
end

begofresp = strfind(iresult, char([13, 10, 13, 10]));

if isempty(begofresp)
    headers = iresult;
    return;
end

headers = iresult(1:begofresp(1)+1);
result = iresult(begofresp(1)+4:end);
status = 1;

hbegin  = strfind(headers, 'HTTP/');
if ~isempty(hbegin)
    headers = headers(hbegin(1):end);
end
hlines = splittocell(headers, char([13,10]), 1, 1);
rcline = splittocell(hlines{1}, ' ', 1);
hrcode = regexpi(rcline{2},'^\d+$');

if ~isempty(hrcode)
    status = str2double(rcline{2});
    switch (rcline{2})
        case {'200', '206'}
            return;
        case {'302'}
            result = '';
            for lc = 2:numel(hlines)
                if isempty(regexpi(hlines{lc}, '^location'))
                    continue;
                end
                loc = regexprep(hlines{lc}, '^location:\s+', '', 'ignorecase');
                if length(loc) > 7 && ...
                    all(lower(loc(1:7)) == 'http://')
                    [result, headers, status] = http_get(loc, copts);
                else
                    [result, headers, status] = http_get(server, loc, copts);
                end
                return;
            end
        otherwise
            result = '';
    end
end
